package com.sikaryofficial.backend.service.address;

import cn.hutool.core.text.CharSequenceUtil;
import com.ip2location.IP2Location;
import com.ip2location.IPResult;
import com.sikaryofficial.backend.constant.RedisCacheKey;
import com.sikaryofficial.backend.domain.entity.Country;
import com.sikaryofficial.backend.domain.vo.CountryAreaVO;
import com.sikaryofficial.common.core.utils.ip.IpUtils;
import com.sikaryofficial.common.redis.service.RedisService;
import com.sikaryofficial.system.api.model.ip.IpLocationVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * @author : qinjinyuan
 * @desc : IP国家信息服务
 * @date : 2024/07/26 16:25
 */
@Service
@Slf4j
@SuppressWarnings(value = "unchecked")
public class Ip2LocationService {

    @Value("${ip2location.ip2location-lite-db1_path}")
    private String db1Path;
    /**
     * 国家 区域 市 三级数据
     */
    @Value("${ip2location.ip2location-lite-db3_path}")
    private String db3Path;
    @Autowired
    private CountryCacheService countryCacheService;

    @Autowired
    private RedisService redisService;

    /**
     * 将IP地址字符串分割成四个部分，每一部分都是一个0到255之间的整数。
     * 将这四个整数分别左移相应的位数，然后相加得到最终的整数值
     *
     * @param ipAddress
     * @return
     */
    private Integer ipToInteger(String ipAddress) {
        return IntStream.range(0, 4)
                .map(i -> (256 - 256 / (int) Math.pow(256, i)) * Integer.parseInt(ipAddress.split("\\.")[i]))
                .reduce(0, Integer::sum);
    }

    private String buildIpDB1CacheKey(String iPAddress) {
        return MessageFormat.format(RedisCacheKey.IP_LOCATION_DB1_CACHE, ipToInteger(iPAddress).toString());
    }

    private String buildIpDB3CacheKey(String iPAddress) {
        return MessageFormat.format(RedisCacheKey.IP_LOCATION_DB3_CACHE, ipToInteger(iPAddress).toString());
    }

    /**
     * 获取国家信息
     * eg:
     *IP Address = 45.61.227.194
     * 	Country Short = US
     * 	Country Long = United States of America
     * 	Region = Colorado
     * 	City = Denver
     * 	ISP = Not_Supported
     * 	Latitude = 0.0
     * 	Longitude = 0.0
     * 	Domain = Not_Supported
     * 	ZipCode = Not_Supported
     * 	TimeZone = Not_Supported
     * 	NetSpeed = Not_Supported
     * 	IDDCode = Not_Supported
     * 	AreaCode = Not_Supported
     * 	WeatherStationCode = Not_Supported
     * 	WeatherStationName = Not_Supported
     * 	MCC = Not_Supported
     * 	MNC = Not_Supported
     * 	MobileBrand = Not_Supported
     * 	Elevation = 0.0
     * 	UsageType = Not_Supported
     * 	AddressType = Not_Supported
     * 	Category = Not_Supported
     * 	District = Not_Supported
     * 	ASN = Not_Supported
     * 	AS = Not_Supported
     *
     * @param iPAddress
     * @return
     */
    public IpLocationVO getGeographyInfoForThird(String iPAddress) {
        if (CharSequenceUtil.isBlank(iPAddress)) {
            return null;
        }
        String cacheKey = buildIpDB3CacheKey(iPAddress);
        if(Boolean.TRUE.equals(redisService.hasKey(cacheKey))){
            log.info("ip2location---cacheKey>{}", cacheKey);
            return redisService.getCacheObject(cacheKey);
        }
        try {
            long startTime = System.currentTimeMillis();
            log.info("ip2location---iPAddress>{}", iPAddress);
            // 加载数据库
            IP2Location db = new IP2Location();
            db.Open(db3Path);
            // 查询 IP 地址
            IPResult ipResult = db.IPQuery(iPAddress);
            IpLocationVO record1 = ipLocationVOMapper(ipResult);
            redisService.setCacheObject(cacheKey, record1, 12L, TimeUnit.HOURS);
            // 关闭数据库
            db.Close();
            log.info("ip2location---cost-->{}", System.currentTimeMillis() - startTime);
            return record1;
        } catch (Exception e) {
            log.error("ip2location error", e);
        }
        return null;
    }

    /**
     * 与上面的方法，区别是查询的数据库文件不一样
     * @param iPAddress
     * @return
     */
    public IpLocationVO getGeographyInfo(String iPAddress) {
        if (CharSequenceUtil.isBlank(iPAddress)) {
            return null;
        }
        String cacheKey = buildIpDB1CacheKey(iPAddress);
        if(Boolean.TRUE.equals(redisService.hasKey(cacheKey))){
            log.info("ip2location---cacheKey>{}", cacheKey);
            return redisService.getCacheObject(cacheKey);
        }
        try {
            long startTime = System.currentTimeMillis();
            log.info("ip2location---iPAddress>{}", iPAddress);
            // 加载数据库
            IP2Location db = new IP2Location();
            db.Open(db1Path);
            // 查询 IP 地址
            IPResult ipResult = db.IPQuery(iPAddress);
            IpLocationVO ipLocationVO = ipLocationVOMapper(ipResult);
            redisService.setCacheObject(cacheKey, ipLocationVO, 12L, TimeUnit.HOURS);
            // 关闭数据库
            db.Close();
            log.info("ip2location---cost-->{}", System.currentTimeMillis() - startTime);
            return ipLocationVO;
        } catch (Exception e) {
            log.error("ip2location error", e);
        }
        return null;
    }

    private IpLocationVO ipLocationVOMapper(IPResult record) {
        IpLocationVO record1 = new IpLocationVO();
        if(Objects.isNull(record)){
            return record1;
        }
        record1.setRegion(record.getRegion());
        record1.setCity(record.getCity());
        record1.setCountryLong(record.getCountryLong());
        record1.setCountryShort(record.getCountryShort());
        return record1;
    }

    public CountryAreaVO getCountryAreaVO() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        String ip = IpUtils.getIpAddr(request);
        IpLocationVO ipLocationVO = getGeographyInfoForThird(ip);
        if (Objects.isNull(ipLocationVO)) {
            return null;
        }
        String countryShort = ipLocationVO.getCountryShort();
        Country country = countryCacheService.getCountryByShortCode(countryShort);
        CountryAreaVO countryAreaVO = new CountryAreaVO();
        countryAreaVO.setArea(ipLocationVO.getRegion());
        countryAreaVO.setCountryCode(ipLocationVO.getCountryShort());
        countryAreaVO.setCountryName(Objects.isNull(country)? null : country.getCountryName());
        countryAreaVO.setCity(ipLocationVO.getCity());
        return countryAreaVO;
    }

    public CountryAreaVO getCountryAreaVO(String ip) {
        IpLocationVO ipLocationVO = getGeographyInfoForThird(ip);
        if (Objects.isNull(ipLocationVO)) {
            return null;
        }
        String countryShort = ipLocationVO.getCountryShort();
        Country country = countryCacheService.getCountryByShortCode(countryShort);
        CountryAreaVO countryAreaVO = new CountryAreaVO();
        countryAreaVO.setArea(ipLocationVO.getRegion());
        countryAreaVO.setCountryCode(ipLocationVO.getCountryShort());
        countryAreaVO.setCountryName(Objects.isNull(country)? null : country.getCountryName());
        countryAreaVO.setCity(ipLocationVO.getCity());
        return countryAreaVO;
    }

    public String getISOCountryCode() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert attributes != null;
        HttpServletRequest request = attributes.getRequest();
        String ip = IpUtils.getIpAddr(request);
        IpLocationVO result = getGeographyInfo(ip);
        if (Objects.isNull(result)) {
            return null;
        }
        String countryShort = result.getCountryShort();
        Country country = countryCacheService.getCountryByShortCode(countryShort);
        if (Objects.isNull(country)) {
            return null;
        }
        return country.getIsoCountryCode();
    }
}
